package pl.edu.agh.jemo.evolution.objfunc.impl.clustering;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import pl.edu.agh.jemo.evolution.genotype.impl.DoubleGenotype;
import pl.edu.agh.jemo.evolution.objfunc.impl.PopulationCentroidObjectiveFunction;
import pl.edu.agh.jemo.evolution.population.Population;
import pl.edu.agh.jemo.evolution.specimen.Specimen;

public class Window {

	private Population[] sortedPopulations;
	private Double[][] windowBoundaries;
	private Set<Specimen> specimensInside = new HashSet<Specimen>();
	private boolean disregard = false;
	private Set<Window> mergeList = new HashSet<Window>();
	private int dimensions;

	public Window(Population[] sortedPopulations, Double[][] windowBoundaries) {
		dimensions = sortedPopulations.length;
		this.sortedPopulations = sortedPopulations;
		resizeWindow(windowBoundaries);
	}

	private void resizeWindow(Double[][] windowBoundaries) {
		this.windowBoundaries = windowBoundaries;
		specimensInside.clear();

		int newLower = find(sortedPopulations[0],windowBoundaries[0][0], 0);
		int newUpper = find(sortedPopulations[0],windowBoundaries[0][1], 0);
		specimensInside.addAll(sortedPopulations[0].subList(newLower, newUpper));
		
		Set<Specimen> toAdd = new HashSet<Specimen>();
		for (int i = 1; i < dimensions; i++) {
			newLower = find(sortedPopulations[i],windowBoundaries[i][0], i);
			newUpper = find(sortedPopulations[i],windowBoundaries[i][1], i);
			toAdd.addAll(sortedPopulations[i].subList(newLower, newUpper));
			specimensInside.retainAll(toAdd);
			toAdd.clear();
		}		
	}
	
	private void resizeWindow(Double[] newCenter) {
		Double[][] newBoundaries = new Double[dimensions][2];
		for (int i = 0 ; i < dimensions ; i++) {
			newBoundaries[i][0] = newCenter[i] - (windowBoundaries[i][1] - windowBoundaries[i][0])/2.; 
			newBoundaries[i][1] = newCenter[i] + (windowBoundaries[i][1] - windowBoundaries[i][0])/2.; 
		}
		resizeWindow(newBoundaries);
	}

	public Set<Specimen> getSpecimensInside() {
		return specimensInside;
	}
	
	private double getEuclideanDistance(Double[] a, Double[] b) {
		double result = 0.;
		for (int i = 0 ; i < dimensions ; i++) {
			result += Math.pow(a[i] - b[i], 2.);
		}
		return Math.sqrt(result);
	}
	
	double centersDistance(Double[] newCenter) {
		/* specimen inside center */
		PopulationCentroidObjectiveFunction centroid = new PopulationCentroidObjectiveFunction();
		Population windowPopulation = new Population();
		windowPopulation.setObjectiveFunctionSet(sortedPopulations[0].getObjectiveFunctionSet());
		windowPopulation.addAll(specimensInside);
		centroid.setPopulationReference(windowPopulation);
		
		/* geometric center */
		Double[] windowCenter = new Double[dimensions];
		for (int i = 0 ; i < dimensions ; i++) {
			windowCenter[i] = (windowBoundaries[i][1] + windowBoundaries[i][0]) / 2.;
			newCenter[i] = centroid.getCenter()[i];
		}
		return getEuclideanDistance(centroid.getCenter(), windowCenter);
	}
	
	public void move(Double treshold) {
		
		Double[] newCenter = new Double[dimensions];
		while (centersDistance(newCenter) > treshold) {
			resizeWindow(newCenter);
		}
	}

	private void copyCoordinates(Double[][] toFill) {
		for (int i = 0 ; i < dimensions ; i++) {
			toFill[i][0] = new Double(windowBoundaries[i][0]);
			toFill[i][1] = new Double(windowBoundaries[i][1]);
			
		}		
	}

	private boolean growthCondition(int beforeStep, double ENLARGMENT_STOP_THRESHOLD) {
		return (((double)specimensInside.size() - (double)beforeStep) / (double)beforeStep) > ENLARGMENT_STOP_THRESHOLD;
	}
	
	public void enlarge(double ENLARGMENT_INCREASE_STEP,
			double ENLARGMENT_STOP_THRESHOLD, double MOVEMENT_THRESHOLD) {
		
		Double[][] currentCoordinates = new Double[dimensions][2];
		copyCoordinates(currentCoordinates);
		int specInsideBefore;
		do {
			specInsideBefore = specimensInside.size();
			for (int i = 0 ; i < dimensions ; i++) {
				// left
				currentCoordinates[i][0] -= ENLARGMENT_INCREASE_STEP;
				resizeWindow(currentCoordinates);
				if (!growthCondition(specInsideBefore, ENLARGMENT_STOP_THRESHOLD)) {
					currentCoordinates[i][0] += ENLARGMENT_INCREASE_STEP;				
					resizeWindow(currentCoordinates);
				}
				// right
				currentCoordinates[i][1] += ENLARGMENT_INCREASE_STEP;
				resizeWindow(currentCoordinates);
				if (!growthCondition(specInsideBefore, ENLARGMENT_STOP_THRESHOLD)) {
					currentCoordinates[i][1] -= ENLARGMENT_INCREASE_STEP;				
					resizeWindow(currentCoordinates);
				}
			}
			move(MOVEMENT_THRESHOLD);
		} while(growthCondition(specInsideBefore, ENLARGMENT_STOP_THRESHOLD));
	}

	public boolean isDisregard() {
		return disregard ;
	}

	public void setDisregard(boolean b) {
		this.disregard = b;
	}

	public int getOverlapingCount(Window window) {
		Set<Specimen> intersection = new HashSet<Specimen>();
		intersection.addAll(specimensInside);
		intersection.retainAll(window.specimensInside);
		
		//System.out.println("==overlapping ==" + intersection.size());
		
		return intersection.size();
	}

	public Set<Window> getMergeList() {
		return mergeList;
	}

	static int find(Population pop, Double val, int dim) { // Zwraca index pierwszy wiekszy (lub rowny)

		if (val < pop.get(0).getGenotype()[dim].asDouble()) {
			return 0;
		} else if (val > pop.get(pop.size() - 1).getGenotype()[dim].asDouble())
			return pop.size();// - 1;

		int a = 0;
		int b = pop.size() - 1;
		int mid = 0;
		while (b - a > 1) {
			mid = (b + a) / 2;
			if (pop.get(mid).getGenotype()[dim].asDouble() < val)
				a = mid;
			else
				b = mid;
		}
		int retVal = (pop.get(a).getGenotype()[dim].asDouble() < val) ? b : a;
		return retVal;
	}
	
	private Double[] getWindowCenter(){
		Double[] windowCenter = new Double[dimensions];
		for (int i = 0 ; i < dimensions ; i++) {
			windowCenter[i] = (windowBoundaries[i][1] + windowBoundaries[i][0]) / 2.;
		}
		return windowCenter;
	}

	public List<Specimen> sort() {
		List<Specimen> retList = new ArrayList<Specimen>();
		retList.addAll(specimensInside);
		Double[] center = getWindowCenter();
		DoubleGenotype[] tempGenotype = new DoubleGenotype[dimensions];
		for (int i = 0 ; i < dimensions ; i++) {
			tempGenotype[i] = new DoubleGenotype(center[i]);
		}
		final Specimen temp = new Specimen();
		temp.setGenotype(tempGenotype);
		Collections.sort(retList, new Comparator<Specimen>() {
			@Override
			public int compare(Specimen o1, Specimen o2) {
				Double dist1 = o1.getEuclideanGenotypeSpaceDistance(temp);
				Double dist2 = o2.getEuclideanGenotypeSpaceDistance(temp);
				
				if (dist1.equals(dist2)) {
					return 0;
				}
				if (dist1 > dist2) {
					return -1;
				}
				return 1;
			}
		});		
		return retList;
	}

}
